
#ifndef __VEC3_H__
#define __VEC3_H__

#include <cmath>
#include <iostream>

class vec3 {
	public : 
		double e[3];

	public : 
		vec3() : e{0, 0, 0} {} 
		vec3(double e0, double e1, double e2) : e{e0, e1, e2} {}

		double x() const { return e[0]; }
		double y() const { return e[1]; }
		double z() const { return e[2]; }
		
		vec3 operator -() const { return vec3{-e[0], -e[1], -e[2]}; }		
		double operator [](int i) const { return e[i]; }
		double& operator [](int i) { return e[i]; } 

		vec3& operator +=(const vec3& vec) { e[0] += vec.e[0]; e[1] += vec.e[1]; e[2] += vec.e[2]; return *this; }
		vec3& operator *=(double t) { e[0] /= t; e[1] /= t; e[2] /= t; return *this; }
		vec3& operator /=(double t) { return *this *= (1 / t); }

		double len() const { return std::sqrt(e[0] * e[0] + e[1] * e[1] + e[2] * e[2]); }
		bool near_zero() { const auto s = 1e-8; return (fabs(e[0]) < s) && (fabs(e[1]) < s) && (fabs(e[2]) < s); }

		inline static vec3 rand() { return vec3{rand_double(), rand_double(), rand_double()}; }
		inline static vec3 rand(double min, double max) { 
			return vec3{rand_double(min, max), rand_double(min, max), rand_double(min, max)};
		}
};

using point3 = vec3;
using color  = vec3;

inline vec3 operator +(const vec3& v, const vec3& u) { return vec3{v.e[0] + u.e[0], v.e[1] + u.e[1], v.e[2] + u.e[2]}; }
inline vec3 operator -(const vec3& v, const vec3& u) { return vec3{v.e[0] - u.e[0], v.e[1] - u.e[1], v.e[2] - u.e[2]}; }
inline vec3 operator *(const vec3& v, const vec3& u) { return vec3{v.e[0] * u.e[0], v.e[1] * u.e[1], v.e[2] * u.e[2]}; }
inline vec3 operator *(double t, const vec3& v) { return vec3{t * v.e[0], t * v.e[1], t * v.e[2]}; }
inline vec3 operator *(const vec3& v, double t) { return t * v; }
inline vec3 operator /(const vec3& v, double t) { return v * (1 / t); }
inline double dot(const vec3& v, const vec3& u) { return v.e[0] * u.e[0] + v.e[1] * u.e[1] + v.e[2] * u.e[2]; }
inline vec3 cross(const vec3 &u, const vec3 &v) {
    return vec3(u.e[1] * v.e[2] - u.e[2] * v.e[1], u.e[2] * v.e[0] - u.e[0] * v.e[2], u.e[0] * v.e[1] - u.e[1] * v.e[0]);
}
inline vec3 unit_vec(const vec3 v) { return v / v.len(); }

inline std::ostream& operator <<(std::ostream& out, const vec3& v) { 
	return out << v.e[0] << " " << v.e[1] << " " << v.e[2]; 
}

inline vec3 rand_in_unit_sphere() { 
	auto p = vec3::rand(-1, 1);
	while (p.len() >= 1) { p = vec3::rand(-1, 1); }
	return p;
}

vec3 rand_unit_vec() { return unit_vec(rand_in_unit_sphere()); }

vec3 rand_in_unit_disk() {
	auto p = vec3{rand_double(-1, 1), rand_double(-1, 1), 0};
	while (p.len() >= 1) { 
		p = vec3{rand_double(-1, 1), rand_double(-1, 1), 0};
	}
	return p;
}

vec3 reflect(const vec3& v, const vec3& n) { return v - 2 * dot(v, n) * n; }

vec3 refract(const vec3& uv, const vec3& n, double etai_over_etat) {
    auto cos_theta = fmin(dot(-uv, n), 1.0);
    vec3 r_out_perp =  etai_over_etat * (uv + cos_theta * n);
    vec3 r_out_parallel = -sqrt(fabs(1.0 - r_out_perp.len() * r_out_perp.len())) * n;
    return r_out_perp + r_out_parallel;
}
#endif 
